use crate::{
    isa::{
        cpu::Cpu,
        rtl::{DIOp, NoArgOp, SDIOp, SDUOp, SSDOp, SSIOp, SSUOp},
        IWordOf, Isa, WordOf,
    },
    memory::{Memory, MemoryRawData},
};
use num_traits::{AsPrimitive, NumCast, One, ToPrimitive, Zero};

fn uint_add_int<T: Isa>(u: WordOf<T>, i: IWordOf<T>) -> usize {
    // 负数转成 uint 然后 + uint 和 直接减效果是
    // 但是这里依赖了 cpu 的具体实现，即用补码表示负数
    u.as_() + i.as_() as usize
}

fn loadi_inner<T: Isa, I: MemoryRawData + AsPrimitive<isize>>(
    rs: usize,
    imm: IWordOf<T>,
    cpu: &T::Cpu,
    mem: &T::Memory,
) -> IWordOf<T> {
    NumCast::from(
        mem.vaddr_get::<I>(uint_add_int::<T>(cpu.reg(rs), imm))
            .as_(),
    )
    .unwrap()
}

fn loadu_inner<T: Isa, U: MemoryRawData + AsPrimitive<usize>>(
    rs: usize,
    imm: IWordOf<T>,
    cpu: &T::Cpu,
    mem: &T::Memory,
) -> WordOf<T> {
    NumCast::from(
        mem.vaddr_get::<U>(uint_add_int::<T>(cpu.reg(rs), imm))
            .as_(),
    )
    .unwrap()
}

fn pc_add_iword<T: Isa>(cpu: &mut T::Cpu, offset: IWordOf<T>) {
    cpu.set_pc(NumCast::from(uint_add_int::<T>(cpu.pc(), offset)).unwrap())
}

pub(super) fn exec_ssd<T: Isa>(cpu: &mut T::Cpu, rs1: usize, rs2: usize, rd: usize, op: SSDOp) {
    match op {
        SSDOp::Add => cpu.set_regi(rd, cpu.regi(rs1) + cpu.regi(rs2)),
        SSDOp::Sub => cpu.set_regi(rd, cpu.regi(rs1) - cpu.regi(rs2)),
        SSDOp::Mul => cpu.set_regi(rd, cpu.regi(rs1) * cpu.regi(rs2)),
        SSDOp::Div => cpu.set_regi(rd, cpu.regi(rs1) / cpu.regi(rs2)),

        SSDOp::Or => cpu.set_reg(rd, cpu.reg(rs1) | cpu.reg(rs2)),
        SSDOp::And => cpu.set_reg(rd, cpu.reg(rs1) & cpu.reg(rs2)),
        SSDOp::Xor => cpu.set_reg(rd, cpu.reg(rs1) ^ cpu.reg(rs2)),

        SSDOp::Slt => {
            if cpu.regi(rs1) < cpu.regi(rs2) {
                cpu.set_reg(rd, WordOf::<T>::one());
            } else {
                cpu.set_reg(rd, WordOf::<T>::zero());
            }
        }
        SSDOp::Sltu => {
            if cpu.reg(rs1) < cpu.reg(rs2) {
                cpu.set_reg(rd, WordOf::<T>::one());
            } else {
                cpu.set_reg(rd, WordOf::<T>::zero());
            }
        }

        SSDOp::Sl => cpu.set_reg(rd, cpu.reg(rs1) << cpu.reg(rs2).as_()),
        SSDOp::Srl => cpu.set_reg(rd, cpu.reg(rs1) >> cpu.reg(rs2).as_()),
        SSDOp::Sra => cpu.set_regi(rd, cpu.regi(rs1) >> cpu.reg(rs2).as_()),
    }
}

pub(super) fn exec_sdi<T: Isa>(
    cpu: &mut T::Cpu,
    mem: &T::Memory,
    rs: usize,
    rd: usize,
    imm: IWordOf<T>,
    op: SDIOp,
) {
    match op {
        SDIOp::Add => cpu.set_regi(rd, cpu.regi(rs) + imm),

        SDIOp::Or => cpu.set_regi(rd, cpu.regi(rs) | imm),
        SDIOp::And => cpu.set_regi(rd, cpu.regi(rs) & imm),
        SDIOp::Xor => cpu.set_regi(rd, cpu.regi(rs) ^ imm),

        SDIOp::Slt => {
            if cpu.regi(rs) < imm {
                cpu.set_reg(rd, WordOf::<T>::one());
            } else {
                cpu.set_reg(rd, WordOf::<T>::zero());
            }
        }

        SDIOp::Jal => {
            cpu.set_reg(rd, cpu.pc() + NumCast::from(4).unwrap());
            pc_add_iword::<T>(cpu, cpu.regi(rs) + imm)
        }

        SDIOp::Loadi1 => cpu.set_regi(rd, loadi_inner::<T, i8>(rs, imm, cpu, mem)),
        SDIOp::Loadi2 => cpu.set_regi(rd, loadi_inner::<T, i16>(rs, imm, cpu, mem)),
        SDIOp::Loadi4 => cpu.set_regi(rd, loadi_inner::<T, i32>(rs, imm, cpu, mem)),
        SDIOp::Loadi8 => cpu.set_regi(rd, loadi_inner::<T, i64>(rs, imm, cpu, mem)),

        SDIOp::Loadu1 => cpu.set_reg(rd, loadu_inner::<T, u8>(rs, imm, cpu, mem)),
        SDIOp::Loadu2 => cpu.set_reg(rd, loadu_inner::<T, u32>(rs, imm, cpu, mem)),
        SDIOp::Loadu4 => cpu.set_reg(rd, loadu_inner::<T, u64>(rs, imm, cpu, mem)),

        SDIOp::Fence => todo!(),
    }
}

pub(super) fn exec_sdu<T: Isa>(
    cpu: &mut T::Cpu,
    mem: &mut T::Memory,
    rs: usize,
    rd: usize,
    imm: WordOf<T>,
    op: SDUOp,
) {
    match op {
        SDUOp::Slt => {
            if cpu.reg(rs) < imm {
                cpu.set_reg(rd, WordOf::<T>::one());
            } else {
                cpu.set_reg(rd, WordOf::<T>::zero());
            }
        }
        SDUOp::Sl => cpu.set_reg(rd, cpu.reg(rs) << imm.as_()),
        SDUOp::Srl => cpu.set_reg(rd, cpu.reg(rs) >> imm.as_()),
        SDUOp::Sra => cpu.set_regi(rd, cpu.regi(rs) >> imm.as_()),
    }
}

pub(super) fn exec_ssi<T: Isa>(
    cpu: &mut T::Cpu,
    mem: &mut T::Memory,
    rs1: usize,
    rs2: usize,
    imm: IWordOf<T>,
    op: SSIOp,
) {
    match op {
        SSIOp::Beq => {
            if cpu.reg(rs1) == cpu.reg(rs2) {
                pc_add_iword::<T>(cpu, imm)
            }
        }
        SSIOp::Bne => {
            if cpu.reg(rs1) != cpu.reg(rs2) {
                pc_add_iword::<T>(cpu, imm)
            }
        }
        SSIOp::Blt => {
            if cpu.regi(rs1) < cpu.regi(rs2) {
                pc_add_iword::<T>(cpu, imm)
            }
        }
        SSIOp::Bge => {
            if cpu.regi(rs1) >= cpu.regi(rs2) {
                pc_add_iword::<T>(cpu, imm)
            }
        }
        SSIOp::Bltu => {
            if cpu.reg(rs1) < cpu.reg(rs2) {
                pc_add_iword::<T>(cpu, imm)
            }
        }
        SSIOp::Bgeu => {
            if cpu.reg(rs1) >= cpu.reg(rs2) {
                pc_add_iword::<T>(cpu, imm)
            }
        }

        // 都是塞寄存器的低 x 位进内存
        SSIOp::Store1 => mem.vaddr_write(
            uint_add_int::<T>(cpu.reg(rs1), imm),
            cpu.reg(rs2).to_u8().unwrap(),
        ),
        SSIOp::Store2 => mem.vaddr_write(
            uint_add_int::<T>(cpu.reg(rs1), imm),
            cpu.reg(rs2).to_u16().unwrap(),
        ),
        SSIOp::Store4 => mem.vaddr_write(
            uint_add_int::<T>(cpu.reg(rs1), imm),
            cpu.reg(rs2).to_u32().unwrap(),
        ),
        SSIOp::Store8 => mem.vaddr_write(
            uint_add_int::<T>(cpu.reg(rs1), imm),
            cpu.reg(rs2).to_u64().unwrap(),
        ),
    }
}

pub(super) fn exec_ssu<T: Isa>(
    cpu: &mut T::Cpu,
    rs1: usize,
    rs2: usize,
    imm: WordOf<T>,
    op: SSUOp,
) {
    match op {}
}

pub(super) fn exec_di<T: Isa>(cpu: &mut T::Cpu, rd: usize, imm: IWordOf<T>, op: DIOp) {
    match op {
        DIOp::LoadI => cpu.set_regi(rd, imm),
        DIOp::Aipc => cpu.set_reg(rd, NumCast::from(uint_add_int::<T>(cpu.pc(), imm)).unwrap()),
        DIOp::Jal => {
            cpu.set_reg(rd, cpu.pc() + NumCast::from(4).unwrap());
            pc_add_iword::<T>(cpu, imm);
        }
    }
}

pub(super) fn exec_noarg<T: Isa>(cpu: &mut T::Cpu, op: NoArgOp) {
    match op {
        NoArgOp::FenceTso => todo!(),
        NoArgOp::Pause => todo!(),
        NoArgOp::ECall => todo!(),
        NoArgOp::EBreak => todo!(),
        NoArgOp::Trap => todo!(),
    }
}
